﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using InterLinq.Base;
using NHibernate;
using NHibernate.Linq;

namespace InterLinq.NHibernate {
	/// <summary>
	/// LINQ for NHibernate specific implementation of the
	/// <see cref="IQueryHandler"/>.
	/// </summary>
	/// <seealso cref="IQueryHandler"/>
	public class NHibernateQueryHandler : IQueryHandler, IBackendProvider {
		#region Fields

		private readonly ISessionFactory m_sessionFactory;
		private ISession m_currentSession;

		#endregion

		#region Properties

		/// <summary>
		/// Gets the <see cref="ISession">CurrentSession</see>.
		/// </summary>
		private ISession CurrentSession {
			get {
				if (m_currentSession == null) {
					StartSession();
				}
				return m_currentSession;
			}
		}

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes this class.
		/// </summary>
		/// <param name="sessionFactory"><see cref="ISessionFactory"/> used for session initialization / disposal.</param>
		public NHibernateQueryHandler(ISessionFactory sessionFactory) {
			if (sessionFactory == null) {
				throw new ArgumentNullException("sessionFactory");
			}
			m_sessionFactory = sessionFactory;
		}

		#endregion

		#region IBackendProvider Members

		public object BeginTransaction() {
			return m_currentSession.BeginTransaction();
		}

		public void CommitTransaction(object transaction) {
			((ITransaction)m_currentSession).Commit();
		}

		public void SaveChanges(IList<BackendProviderChangeSet> Changes) {
			StartSession();
			m_currentSession.Transaction.Begin();
			foreach (BackendProviderChangeSet change in Changes) {
				m_currentSession.SaveOrUpdate(change.obj);
			}
			m_currentSession.Transaction.Commit();
			CloseSession();

			//TODO: doesn't this need a comit?? m_currentSession.comit
		}

		public void SaveChanges(IList<BackendProviderDeleteSet> Changes) {
			StartSession();
			m_currentSession.Transaction.Begin();
			foreach (BackendProviderDeleteSet change in Changes) {
				m_currentSession.Delete(change.obj);
			}
			m_currentSession.Transaction.Commit();
			CloseSession();

			//TODO: doesn't this need a comit?? m_currentSession.comit
		}

		public void SaveChanges(IList<BackendProviderNewSet> Changes) {
			StartSession();
			m_currentSession.Transaction.Begin();
			foreach (BackendProviderNewSet change in Changes) {
				m_currentSession.Save(change.obj);
			}
			m_currentSession.Transaction.Commit();
			CloseSession();

			//TODO: doesn't this need a comit?? m_currentSession.comit
		}

		#endregion

		#region IQueryHandler Members

		public ReaderWriterLock LockOfDataSource {
			get { return null; }
		}

		/// <summary>
		/// Returns an <see cref="IQueryable{T}"/>.
		/// </summary>
		/// <typeparam name="T">Generic Argument of the returned <see cref="IQueryable{T}"/>.</typeparam>
		/// <returns>Returns an <see cref="IQueryable{T}"/>.</returns>
		/// <seealso cref="IQueryHandler.GetTable{T}"/>
		public IQueryable<T> GetTable<T>() where T : class {
			return CurrentSession.Linq<T>();
		}

		/// <summary>
		/// Returns an <see cref="IQueryable{T}"/>.
		/// </summary>
		/// <param name="type">Type of the returned <see cref="IQueryable{T}"/>.</param>
		/// <returns>Returns an <see cref="IQueryable{T}"/>.</returns>
		/// <seealso cref="IQueryHandler.GetTable"/>
		public IQueryable GetTable(Type type) {
			MethodInfo getTableMethod = GetType().GetMethod("GetTable", BindingFlags.Instance | BindingFlags.Public, null,
															  new Type[0], null);
			MethodInfo genericGetTableMethod = getTableMethod.MakeGenericMethod(type);
			return (IQueryable)genericGetTableMethod.Invoke(this, new object[0]);
		}

		/// <summary>
		/// Tells the <see cref="IQueryHandler"/> to start a new the session.
		/// </summary>
		/// <returns>True, if the session creation was successful. False, if not.</returns>
		/// <seealso cref="IQueryHandler.StartSession"/>
		public bool StartSession() {
			if (m_currentSession == null) {
				m_currentSession = m_sessionFactory.OpenSession();
			}
			return true;
		}

		/// <summary>
		/// Tells the <see cref="IQueryHandler"/> to close the current session.
		/// </summary>
		/// <returns>True, if the session closing was successful. False, if not.</returns>
		/// <seealso cref="IQueryHandler.CloseSession"/>
		public bool CloseSession() {
			if (m_currentSession != null) {
				m_currentSession.Close();
				m_currentSession = null;
			}
			return true;
		}

		public IEnumerable<Type> GetTableTypes() {
			throw new NotImplementedException();
		}

		#endregion
	}
}